Tester.php
<?php
namespace Tlf;
class Tester {
use Assertions;
use Tester\Databasing;
use Tester\UtilityTrait;
protected $benches;
protected $catchers = [];
/**
* Comparisons from a single test. Should be reset between tests.
*/
protected $comparisons = ['true' => 0, 'false'=>0];
protected $disabled = false;
protected $results = [];
protected $options = [];
/**
* The cli class used to run the tests
*/
public $cli = null;
/**
* @param $options usually args passed from the command line.
*/
public function __construct(array $options=[], $cli=null){
$this->options = $options;
$this->prepare();
if ($this->options['set_error_handler']??true){
set_error_handler([$this,'throwError']);
}
$this->cli = $cli;
}
public function throwError($errno, $errstr, $errfile, $errline) {
throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
}
public function disable(){
$this->disabled = true;
}
/**
* Call `$tester->test('TestName')->compare($target,$actual)` to run a sub-test inside a test in your class
* Chaining is not required
*
* @export(Usage.test)
*/
protected function test($subTestName){
echo "\n\n****subtest: $subTestName****";
return $this;
}
protected function startTest($key){
echo "\n ".'inner test \''.$key.'\' starts:';
}
protected function endTest($key,$result){
echo "\n ".'inner test \''.$key.'\' ends with result: <b>'.($result ? 'true' : 'false').'</b>';
}
/**
* Get an array of method names on $this that start with 'test'
*/
protected function getTestMethods(){
$class = get_class($this);
$check_class = $this->options['class']??'';
if (isset($this->options['class'])
&&substr($class,-strlen($check_class))!=$check_class
){
return [];
}
$methods = get_class_methods($this);
$methods = array_map(function($key,$value) //use ($builtInMethods)
{
if ($value=='test')return null;
if (substr($value,0,4)!='test')return null;
return $value;
},array_keys($methods),array_values($methods));
return $methods;
}
protected function startOb(){
return \Tlf\Tester\Utility::startOb();
}
protected function endOb($ob_level){
return \Tlf\Tester\Utility::endOb($ob_level);
}
/**
* Run tests
*
* @param $methods an array of method names to run as tests or NULL to run all methods beginning with 'test'
*/
public function run(?array $methods = null){
if ($methods===null)$methods = $this->options['test'] ?? $this->getTestMethods();
// if ($methods===null)$methods = $this->getTestMethods();
//
// var_dump($this->options['test']);
$testsPassingCount=0;
$testsFailingCount=0;
$testsRunCount=0;
// $this->results...
foreach ($methods as $method){
if ($method==NULL)continue;
$this->inverted = false;
if (!method_exists($this, $method)&&substr($method,0,4)!='test'){
$method = 'test'.ucfirst($method);
}
if (!method_exists($this, $method)){
continue;
}
$result = ['method'=>substr($method,4), 'error'=>null];
$this->comparisons = ['true'=>0, 'false'=>0];
$this->catchers = [];
$error = null;
$retValue = null;
$this->benchStart('test_'.$method);
$ob_level = $this->startOb();
try {
$testsRunCount++;
$result['returnVal'] = $this->$method();
} catch (\Throwable $t){
$result['returnVal'] = null;
$result['error'] = $t;
}
$result['output'] = $this->endOb($ob_level);
$result['bench'] = $this->benchEnd('test_'.$method);
if ($result['error']!==null||$result['returnVal']===false||$this->comparisons['false']>0||$this->comparisons['true']==0){
$result['result'] = false;
$testsFailingCount++;
} else {
$testsPassingCount++;
$result['result'] = true;
}
$result['disabled'] = $this->disabled;
$this->disabled = false;
if (($c=count($this->catchers))>0){
// $result = false;
$result['output'] = "---exception-fail---\n{$c} exceptions were not handled.\n----------\n".$result['output'];
}
$result['html'] = $this->htmlOutput((object)$result);
$this->results[$method] = $result;
}
$this->results['_testsPassing'] = $testsPassingCount;
$this->results['_testsFailing'] = $testsFailingCount;
$this->results['_testsRun'] = $testsRunCount;
return $this->results;
//now do what we do with results.
}
public function benchStart($key){
$this->benches[$key]['start'] = microtime(true);
}
public function benchEnd($key){
$end = microtime(true);
$start = $this->benches[$key]['start'];
unset($this->benches[$key]);
$diff = $end - $start;
return (object)[
'start'=>$start,
'end'=>$end,
'diff'=>$diff
];
}
//@export_start(Example.ModifyOutput)
public function htmlOutput($details){
ob_start();
$successStatement = $details->result ? '<span style="color:green;">success</span>' : '<span style="color:red;">fail</span>';
if ($details->error!=null)$successStatement = '<strong style="color:blue;">error</strong>';
if ($details->disabled===true)$successStatement = '<strong style="color:orange;">disabled</strong>';
$diff = $details->bench->diff;
if ($diff < 0.0001)$diff = '';
else $diff = 'in '.number_format($diff*1000,3).'ms';
echo "<details>\n <summary><b>".$details->method.":</b> ".$successStatement." {$diff} </summary>\n";
// echo " <div>Time to run: ".$details->bench->diff."</div>";
echo " <div style='padding-left:4ch;white-space:pre;'>\n";
$detailsOutput = htmlentities($details->output);
$detailsLines = explode("\n",$detailsOutput);
$detailsLines = array_map(function($value){return ' '.$value;},$detailsLines);
echo implode("\n",$detailsLines);
// var_dump($detailsLines);
echo "\n </div>";
if ($details->error!=null){
echo "\n <br>\n";
echo " <div style='color:red;padding-left:4ch;white-space:pre;'>\n";
$errorOutput = $details->error;
$errorLines = explode("\n",$errorOutput);
$errorLines = array_map(function($value){return ' '.$value;},$errorLines);
echo implode("\n",$errorLines);
echo "\n </div>";
}
echo "\n</details>\n";
return ob_get_clean();
}
//@export_end(Example.ModifyOutput)
public function prepare(){
}
/**
* On a class that extends `\Taeluf\Tester`, call `ExtendingClass::runAll()` to run the tests.
*
* @deprecated this function no longer does anything.
* @export(RunTests.All)
*/
static public function runAll(){
// $tester = new static();
// $tester->run();
// return $tester;
}
/**
*
* @deprecated this function no longer does anything
*/
static public function runTests($name){
// $tester = new static();
// $tester->run($name);
// return $tester;
}
/**
*
* @deprecated this function no longer does anything
*/
static public function runAllToFile($filePath,$andPrint = true){
// ob_start();
// $tester = static::runAll();
// $output = ob_get_clean();
// if ($andPrint)echo $output;
// file_put_contents($filePath, $output);
//
// return $tester;
}
/**
* Call `Taeluf\Tester::xdotoolRefreshFirefox($switchBackToCurrWindow = false)` to refresh your browser tab.
* If you're writing you're using `runAllToFile($file)`, this could come in handy.
*
* @deprecated in favor of \Taeluf\Tester\Utility::xdotoolRefreshFirefox()
* @export(Extra.RefreshBrowserTab)
*/
static public function xdotoolRefreshFirefox($switchBackToWindow = false){
$args = $switchBackToWindow ? ' y' : '';
system(__DIR__.'/reload.sh'.$args);
\Tlf\Tester\Utility::xdotoolRefreshFirefox($switchBackToWindow);
}
}